﻿using System;
using System.Configuration;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using MS.InformationManagement.DocumentIDServiceApp.Contracts;

namespace MS.InformationManagement.DocumentIDServiceApp
{
    [IisWebServiceApplicationProxyBackupBehavior]
    [Guid("E8135AF5-C7E5-450F-A427-383F8ED0379D")]
    public sealed class DocumentIDServiceApplicationProxy:SPIisWebServiceApplicationProxy
    {
        private ChannelFactory<IDocumentIDService> _channelFactory;
        private Object _channelFactorySyncLock = new Object();
        private String _endpointConfigurationName;

        [Persisted]
        private SPServiceLoadBalancer _loadBalancer;

        #region [Constructors]

        // An empty public constructor required for serialization.
        public DocumentIDServiceApplicationProxy()
            : base()
        {
        }

        public DocumentIDServiceApplicationProxy(string name, SPIisWebServiceProxy serviceProxy, Uri serviceEndpointUri)
            : base(name, serviceProxy, serviceEndpointUri)
        {
            // Create a new round-robin load balancer
            _loadBalancer = new SPRoundRobinServiceLoadBalancer(serviceEndpointUri);
        }

        #endregion

        #region [SPIisWebServiceApplicationProxy]

        public override string TypeName
        {
            get { return ("DocumentID Service Application Proxy"); }
        }

        public override void Provision()
        {
            // Provision the load balancer
            _loadBalancer.Provision();

            base.Provision();

            // Ensure the provisioned load balancer gets persisted back to the database  
            this.Update();
        }

        public override void Unprovision(bool deleteData)
        {
            // Unprovision the load balancer
            _loadBalancer.Unprovision();

            base.Unprovision(deleteData);

            // Ensure the un-provisioned load balancer gets persisted back to the database  
            this.Update();
        }

        #endregion

        #region [WCF code plumbing]

        private ChannelFactory<T> CreateChannelFactory<T>(string endpointConfigurationName)
        {
            String clientConfigPath = SPUtility.GetGenericSetupPath(@"WebClients\DocumentIDService");
            Configuration clientConfig = OpenClientConfiguration(clientConfigPath);
            ConfigurationChannelFactory<T> factory = new ConfigurationChannelFactory<T>(endpointConfigurationName, clientConfig, null);

            // Configure the channel factory for claims authentication
            factory.ConfigureCredentials(SPServiceAuthenticationMode.Claims);

            return (factory);
        }

        internal delegate void CodeToRunOnApplicationProxy(DocumentIDServiceApplicationProxy applicationProxy);

        internal static void Invoke(SPServiceContext serviceContext, CodeToRunOnApplicationProxy codeBlock)
        {
            if (serviceContext == null)
                throw new ArgumentNullException("serviceContext");

            DocumentIDServiceApplicationProxy proxy =
                (DocumentIDServiceApplicationProxy)serviceContext.GetDefaultProxy(typeof(DocumentIDServiceApplicationProxy));

            if (proxy == null)
                throw new InvalidOperationException("DocumentID Service Application proxy not found!");

            // Ensure that SPServiceContext.Current is set correctly.
            using (new SPServiceContextScope(serviceContext))
            {
                // Execute the delegate
                codeBlock(proxy);
            }
        }

        private delegate void CodeToRunOnChannel(IDocumentIDService serviceContract);

        private void ExecuteOnChannel(String operationName, ExecuteOptions options, CodeToRunOnChannel codeBlock)
        {
            using (new SPMonitoredScope("ExecuteOnChannel:" + operationName))
            {
                SPServiceLoadBalancerContext loadBalancerContext = _loadBalancer.BeginOperation();
                try
                {
                    // Get a channel to the service application endpoint
                    IChannel channel = (IChannel)GetChannel(loadBalancerContext.EndpointAddress, options);
                    try
                    {
                        // Execute the code
                        codeBlock((IDocumentIDService)channel);
                        channel.Close();
                    }
                    catch (TimeoutException)
                    {
                        loadBalancerContext.Status = SPServiceLoadBalancerStatus.Failed;
                        throw;
                    }
                    catch (EndpointNotFoundException)
                    {
                        loadBalancerContext.Status = SPServiceLoadBalancerStatus.Failed;
                        throw;
                    }
                    finally
                    {
                        if (channel.State != CommunicationState.Closed)
                            channel.Abort();
                    }
                }
                finally
                {
                    loadBalancerContext.EndOperation();
                }
            }
        }

        private string GetEndpointConfigurationName(Uri address)
        {
            if (address == null)
                throw new ArgumentNullException("address");

            string configName;

            if (address.Scheme == Uri.UriSchemeHttp)
            {
                configName = "http";
            }
            else if (address.Scheme == Uri.UriSchemeHttps)
            {
                configName = "https";
            }
            //else if (address.Scheme == Uri.UriSchemeNetTcp)
            //{
            //    configName = "tcp";
            //}
            //else if (address.Scheme == Uri.UriSchemeNetPipe)
            //{
            //    configName = "pipe";
            //}
            else
            {
                throw new NotSupportedException("Unsupported endpoint address");
            }

            return (configName);
        }

        private IDocumentIDService GetChannel(Uri address, ExecuteOptions options)
        { 
            if (address == null)
                throw new ArgumentNullException("address");

            string endpointConfigName = GetEndpointConfigurationName(address);

            // Singleton to get the ChannelFactory instance
            if ((_channelFactory == null) || (endpointConfigName != _endpointConfigurationName))
            {
                lock (_channelFactorySyncLock)
                {
                    if ((_channelFactory == null) || (endpointConfigName != _endpointConfigurationName))
                    {
                        _channelFactory = CreateChannelFactory<IDocumentIDService>(endpointConfigName);
                        _endpointConfigurationName = endpointConfigName;
                    }
                }
            }

            IDocumentIDService channel;

            if (ExecuteOptions.AsProcess == (options & ExecuteOptions.AsProcess))
            {
                // Create a channel that acts as the process when authenticating with the service
                channel = _channelFactory.CreateChannelAsProcess<IDocumentIDService>(new EndpointAddress(address));
            }
            else
            {
                // Create a channel that acts as the logged on user when authenticating with the service
                channel = _channelFactory.CreateChannelActingAsLoggedOnUser<IDocumentIDService>(new EndpointAddress(address));
            }

            return (channel);
        }

        #endregion

        #region [IDocumentIDService]

        public void AddDocumentIDBank(string bankName, ExecuteOptions options)
        {
            ExecuteOnChannel("AddDocumentIDBank", options, channel => channel.AddDocumentIDBank(bankName));
        }

        public void DeleteDocumentIDBank(string bankName, ExecuteOptions options)
        {
            ExecuteOnChannel("DeleteDocumentIDBank", options, channel => channel.DeleteDocumentIDBank(bankName));
        }

        public void ResetDocumentIDBank(string bankName, ExecuteOptions options)
        {
            ExecuteOnChannel("ResetDocumentIDBank", options, channel => channel.ResetDocumentIDBank(bankName));
        }

        public int GetNextDocumentID(string bankName, ExecuteOptions options)
        {
            int result = 0;
            ExecuteOnChannel("GetNextDocumentID", options, channel => result = channel.GetNextDocumentID(bankName));
            return (result);
        }

        #endregion

        #region [Custom implementation]

        public void AddToDefaultGroup()
        {
            SPServiceApplicationProxyGroup group = SPServiceApplicationProxyGroup.Default;
            group.Add(this);
            group.Update();
        }
        
        #endregion
    }
}
